import requests
import socket
import qbittorrentapi
from qbittorrentapi import LoginFailed, APIConnectionError as QBConnectionError


class QBittorrentManager:
    """qBittorrent管理工具类，仅支持通过种子文件内容添加下载"""
    
    # 种子状态映射（友好名称转换）
    _TORRENT_STATE_MAP = {
        "error": "错误",
        "missingFiles": "文件缺失",
        "uploading": "上传中",
        "pausedUP": "已暂停（上传）",
        "queuedUP": "排队中（上传）",
        "stalledUP": "停滞（上传）",
        "checkingUP": "校验中（上传）",
        "forcedUP": "强制上传",
        "downloading": "下载中",
        "pausedDL": "已暂停（下载）",
        "queuedDL": "排队中（下载）",
        "stalledDL": "停滞（下载）",
        "checkingDL": "校验中（下载）",
        "forcedDL": "强制下载",
        "checkingResumeData": "校验恢复数据",
        "moving": "移动中",
        "unknown": "未知"
    }

    def __init__(self, config):
        """初始化管理器
        
        Args:
            config: 连接配置字典，需包含host、port，可选username、password
        """
        self.config = self._validate_config(config)
        self.client = None
        self.last_error = None

    def _validate_config(self, config):
        """验证配置有效性并设置默认值"""
        required_keys = ['host', 'port']
        for key in required_keys:
            if key not in config:
                raise ValueError(f"配置缺少必要项: {key}")
        
        return {
            'host': config['host'],
            'port': int(config['port']),
            'username': config.get('username', 'admin'),
            'password': config.get('password', 'adminadmin')
        }

    def test_connection(self):
        """使用Socket测试服务器连接性（仅检查端口可达性）"""
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
                s.settimeout(10)
                result = s.connect_ex((self.config['host'], self.config['port']))
                return result == 0
        except Exception as e:
            self.last_error = f"连接测试失败: {str(e)}"
            return False

    def is_connected(self):
        """检查当前API连接是否有效"""
        if not self.client:
            return False
        try:
            self.client.app_version()
            return True
        except (QBConnectionError, LoginFailed) as e:
            self.last_error = f"连接失效: {str(e)}"
            return False

    def connect(self, max_retries=2):
        """连接到qBittorrent服务器，支持重试机制"""
        if self.is_connected():
            return True

        # 先进行端口连通性测试
        if not self.test_connection():
            self.last_error = f"无法连接到 {self.config['host']}:{self.config['port']}"
            return False

        retry_count = 0
        self.last_error = None
        
        while retry_count <= max_retries:
            try:
                self.client = qbittorrentapi.Client(
                    host=self.config['host'],
                    port=self.config['port'],
                    username=self.config['username'],
                    password=self.config['password']
                )
                self.client.auth_log_in()
                return True
                
            except LoginFailed as e:
                self.last_error = f"认证失败: 用户名或密码错误"
                break  # 认证失败无需重试
            except QBConnectionError as e:
                self.last_error = f"连接失败（{retry_count+1}/{max_retries+1}）: {str(e)}"
            except Exception as e:
                self.last_error = f"连接异常（{retry_count+1}/{max_retries+1}）: {str(e)}"
            
            retry_count += 1

        self.client = None
        return False

    def get_downloads(self, filter_state=None):
        """获取下载列表，支持按状态过滤"""
        if not self.connect():
            return []

        try:
            torrents = self.client.torrents_info(status_filter=filter_state)
            return [self._format_torrent_info(torrent) for torrent in torrents]
            
        except QBConnectionError as e:
            self.last_error = f"连接异常: {str(e)}"
            self.connect()  # 尝试重新连接
        except Exception as e:
            self.last_error = f"获取下载列表失败: {str(e)}"
        return []

    def add_torrent(
            self, 
            torrent_content,
            save_path=None,
            tags=None,
            start_paused=False
        ):
        """添加种子下载，仅支持通过种子文件内容添加"""
        if not self.connect():
            return {
                'success': False, 
                'message': f"未连接到服务器: {self.last_error}"
            }

        # 验证输入（仅接受种子文件内容）
        if not torrent_content:
            return {'success': False, 'message': "必须提供torrent_content（种子文件内容）"}

        try:
            add_kwargs = {
                'paused': start_paused,
                'save_path': save_path,
                'tags': ','.join(tags) if tags else None
            }
            
            # 移除None值参数
            add_kwargs = {k: v for k, v in add_kwargs.items() if v is not None}
            
            # 仅支持通过文件内容添加种子
            self.client.torrents_add(torrent_files=torrent_content, **add_kwargs)
            
            return {
                'success': True, 
                'message': "种子文件添加成功"
            }
            
        except QBConnectionError as e:
            self.last_error = f"连接异常: {str(e)}"
            self.connect()
            return {'success': False, 'message': f"连接异常: {str(e)}"}
        except Exception as e:
            return {'success': False, 'message': f"添加失败: {str(e)}"}

    def download_and_add_torrent(self, url, save_path=None, tags=None, start_paused=False):
        """从URL下载种子文件并添加到下载队列（组合操作）"""
        # 先下载种子文件
        torrent_content = self._download_torrent_from_url(url)
        if not torrent_content:
            return {
                'success': False, 
                'message': f"无法获取种子文件: {self.get_last_error()}"
            }
            
        # 再添加下载
        return self.add_torrent(
            torrent_content=torrent_content,
            save_path=save_path,
            tags=tags,
            start_paused=start_paused
        )

    def _download_torrent_from_url(self, url):
        """从URL下载种子文件内容（带验证）"""
        try:
            headers = {
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
            }
            
            response = requests.get(url, headers=headers, timeout=30)
            response.raise_for_status()
            
            content_type = response.headers.get('Content-Type', '')
            if not (content_type == 'application/x-bittorrent' or url.endswith('.torrent')):
                self.last_error = f"非种子文件类型: {content_type}"
                return None
                
            return response.content
            
        except Exception as e:
            self.last_error = f"URL下载失败: {str(e)}"
            return None

    def _format_torrent_info(self, torrent):
        """格式化种子信息为友好字典"""
        return {
            'name': torrent.name,
            'progress': round(torrent.progress * 100, 1),
            'status': self._TORRENT_STATE_MAP.get(torrent.state, torrent.state),
            'raw_status': torrent.state,
            'size': torrent.total_size,
            'size_human': self._format_size(torrent.total_size),
            'downloaded': torrent.downloaded,
            'uploaded': torrent.uploaded,
            'save_path': torrent.save_path,
            'eta': torrent.eta,
            'eta_human': self._format_eta(torrent.eta)
        }

    @staticmethod
    def _format_size(size_bytes):
        """字节大小转换为人类可读格式"""
        units = ['B', 'KB', 'MB', 'GB', 'TB']
        size = size_bytes
        unit_index = 0
        while size >= 1024 and unit_index < len(units) - 1:
            size /= 1024
            unit_index += 1
        return f"{size:.2f} {units[unit_index]}"

    @staticmethod
    def _format_eta(seconds):
        """秒数转换为人类可读的ETA格式"""
        if seconds < 0:
            return "未知"
        if seconds == 0:
            return "已完成"
            
        minutes, seconds = divmod(seconds, 60)
        hours, minutes = divmod(minutes, 60)
        days, hours = divmod(hours, 24)
        
        parts = []
        if days > 0:
            parts.append(f"{days}天")
        if hours > 0:
            parts.append(f"{hours}时")
        if minutes > 0:
            parts.append(f"{minutes}分")
        parts.append(f"{seconds}秒")
        
        return "".join(parts)

    def get_last_error(self):
        """获取最后一次错误信息"""
        return self.last_error or "无错误"

    def close(self):
        """关闭连接释放资源"""
        if self.client:
            try:
                self.client.auth_log_out()
            except Exception as e:
                self.last_error = f"关闭连接错误: {str(e)}"
            self.client = None

    def __del__(self):
        """对象销毁时自动关闭连接"""
        self.close()
